package ace.cmp.security.oauth2.resource.server.core.introspection;

import java.time.Instant;
import java.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal;
import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames;
import org.springframework.security.oauth2.server.resource.introspection.OAuth2IntrospectionAuthenticatedPrincipal;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;

/**
 * @author caspar
 * @date 2023/3/10 11:06 不透明令牌 + 获取用户信息,内存模式，用于测试用途
 */
@Slf4j
public class InMemoryOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
  private static final String AUTHORITY_PREFIX = "SCOPE_";
  private final Map<String, Object> claims;

  public InMemoryOpaqueTokenIntrospector(Map<String, Object> claims) {
    this.claims = claims;
  }

  @Override
  public OAuth2AuthenticatedPrincipal introspect(String token) {
    OAuth2AuthenticatedPrincipal authorized = this.convertClaimsSet(new HashMap<>(this.claims));
    return authorized;
  }

  /**
   * copy from {@link SpringOpaqueTokenIntrospector#convertClaimsSet(Map)}
   *
   * @param claims
   * @return
   */
  private OAuth2AuthenticatedPrincipal convertClaimsSet(Map<String, Object> claims) {
    claims.computeIfPresent(
        OAuth2TokenIntrospectionClaimNames.AUD,
        (k, v) -> {
          if (v instanceof String) {
            return Collections.singletonList(v);
          }
          return v;
        });
    claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.CLIENT_ID, (k, v) -> v.toString());
    claims.computeIfPresent(
        OAuth2TokenIntrospectionClaimNames.EXP,
        (k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
    claims.computeIfPresent(
        OAuth2TokenIntrospectionClaimNames.IAT,
        (k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
    // RFC-7662 page 7 directs users to RFC-7519 for defining the values of these
    // issuer fields.
    // https://datatracker.ietf.org/doc/html/rfc7662#page-7
    //
    // RFC-7519 page 9 defines issuer fields as being 'case-sensitive' strings
    // containing
    // a 'StringOrURI', which is defined on page 5 as being any string, but strings
    // containing ':'
    // should be treated as valid URIs.
    // https://datatracker.ietf.org/doc/html/rfc7519#section-2
    //
    // It is not defined however as to whether-or-not normalized URIs should be
    // treated as the same literal
    // value. It only defines validation itself, so to avoid potential ambiguity or
    // unwanted side effects that
    // may be awkward to debug, we do not want to manipulate this value. Previous
    // versions of Spring Security
    // would *only* allow valid URLs, which is not what we wish to achieve here.
    claims.computeIfPresent(OAuth2TokenIntrospectionClaimNames.ISS, (k, v) -> v.toString());
    claims.computeIfPresent(
        OAuth2TokenIntrospectionClaimNames.NBF,
        (k, v) -> Instant.ofEpochSecond(((Number) v).longValue()));
    Collection<GrantedAuthority> authorities = new ArrayList<>();
    claims.computeIfPresent(
        OAuth2TokenIntrospectionClaimNames.SCOPE,
        (k, v) -> {
          if (v instanceof String) {
            Collection<String> scopes = Arrays.asList(((String) v).split(" "));
            for (String scope : scopes) {
              authorities.add(new SimpleGrantedAuthority(AUTHORITY_PREFIX + scope));
            }
            return scopes;
          }
          return v;
        });
    return new OAuth2IntrospectionAuthenticatedPrincipal(claims, authorities);
  }
}
